001 /*
002 * Copyright 2004 Niclas Hedhman
003 * Copyright 2004-2005 Stephen McConnell
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.
015 *
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020 package net.dpml.tools.tasks;
021
022 import java.io.File;
023 import java.io.FileFilter;
024 import java.io.FileInputStream;
025 import java.io.IOException;
026 import java.text.SimpleDateFormat;
027 import java.util.Calendar;
028 import java.util.Hashtable;
029 import java.util.Iterator;
030 import java.util.Map;
031 import java.util.Date;
032 import java.util.regex.Matcher;
033 import java.util.regex.Pattern;
034
035 import javax.xml.transform.Transformer;
036 import javax.xml.transform.TransformerFactory;
037 import javax.xml.transform.stream.StreamResult;
038 import javax.xml.transform.stream.StreamSource;
039
040 import net.dpml.transit.Transit;
041
042 import org.apache.tools.ant.BuildException;
043 import org.apache.tools.ant.Project;
044 import org.apache.tools.ant.taskdefs.Copy;
045 import org.apache.tools.ant.types.FileSet;
046
047 /**
048 * Site generation.
049 *
050 * @author <a href="http://www.dpml.net">The Digital Product Meta Library</a>
051 * @version 1.0.0
052 */
053 public class DocTask extends GenericTask
054 {
055 private static final String ORG_NAME_VALUE = "The Digital Product Meta Library";
056 private static final String DOC_TEMP_VALUE = "docs";
057 private static final String DOC_SRC_VALUE = "docs";
058 private static final String DOC_RESOURCES_VALUE = "resources";
059 private static final String DOC_THEME_VALUE = "formal";
060 private static final String DOC_FORMAT_VALUE = "html";
061 private static final String DOC_DATE_FORMAT_VALUE = "yyyy-MMM-dd";
062 private static final String DOC_STYLE_VALUE = "standard";
063 private static final String DOC_ENTRY_VALUE = "";
064 private static final String DOC_LOGO_RIGHT_FILE_VALUE = "";
065 private static final String DOC_LOGO_RIGHT_URL_VALUE = "";
066 private static final String DOC_LOGO_LEFT_FILE_VALUE = "";
067 private static final String DOC_LOGO_LEFT_URL_VALUE = "";
068 private static final String DOC_LOGO_MIDDLE_FILE_VALUE = "";
069 private static final String DOC_LOGO_MIDDLE_URL_VALUE = "";
070 private static final String DOC_BRAND_NAME_VALUE = "DPML";
071 private static final String DOC_HOME_PATH_VALUE = "index.html";
072
073 /**
074 * Constant organization key.
075 */
076 public static final String ORG_NAME_KEY = "project.organization.name";
077
078 /**
079 * Constant temp docs key.
080 */
081 public static final String DOC_TEMP_KEY = "project.target.temp.docs";
082
083 /**
084 * Constant docs src key.
085 */
086 public static final String DOC_SRC_KEY = "project.docs.src";
087
088 /**
089 * Constant docs resources key.
090 */
091 public static final String DOC_RESOURCES_KEY = "project.docs.resources";
092
093 /**
094 * Constant docs theme key.
095 */
096 public static final String DOC_THEME_KEY = "project.docs.theme";
097
098 /**
099 * Constant docs output format key.
100 */
101 public static final String DOC_FORMAT_KEY = "project.docs.output.format";
102
103 /**
104 * Constant docs date format key.
105 */
106 public static final String DOC_DATE_FORMAT_KEY = "project.docs.date.format";
107
108 /**
109 * Constant docs output style key.
110 */
111 public static final String DOC_STYLE_KEY = "project.docs.output.style";
112
113 /**
114 * Constant docs entry-point key.
115 */
116 public static final String DOC_ENTRY_KEY = "project.docs.entry-point";
117
118 /**
119 * Constant docs logo-right-file key.
120 */
121 public static final String DOC_LOGO_RIGHT_FILE_KEY = "project.docs.logo.right.file";
122
123 /**
124 * Constant docs logo-right url key.
125 */
126 public static final String DOC_LOGO_RIGHT_URL_KEY = "project.docs.logo.right.url";
127
128 /**
129 * Constant docs logo-left file key.
130 */
131 public static final String DOC_LOGO_LEFT_FILE_KEY = "project.docs.logo.left.file";
132
133 /**
134 * Constant docs logo-left url key.
135 */
136 public static final String DOC_LOGO_LEFT_URL_KEY = "project.docs.logo.left.url";
137
138 /**
139 * Constant docs logo-middle file key.
140 */
141 public static final String DOC_LOGO_MIDDLE_FILE_KEY = "project.docs.logo.middle.file";
142
143 /**
144 * Constant docs logo-middle url key.
145 */
146 public static final String DOC_LOGO_MIDDLE_URL_KEY = "project.docs.logo.middle.url";
147
148 /**
149 * Constant docs brand key.
150 */
151 public static final String DOC_BRAND_NAME_KEY = "project.docs.brand.name";
152
153 /**
154 * Constant docs anchor url key.
155 */
156 public static final String DOC_ANCHOR_URL_KEY = "project.docs.anchor.url";
157
158 /**
159 * Constant docs home page path.
160 */
161 public static final String DOC_HOME_PATH_KEY = "project.docs.home.path";
162
163 private String m_theme;
164 private File m_baseToDir;
165 private File m_baseSrcDir;
166 private File m_dest;
167
168 /**
169 * Return the assigned theme.
170 * @return the theme
171 */
172 public String getTheme()
173 {
174 if( m_theme != null )
175 {
176 return m_theme;
177 }
178 String theme = getProject().getProperty( DOC_THEME_KEY );
179 if( null != theme )
180 {
181 return theme;
182 }
183 else
184 {
185 return getResource().getProperty( DOC_THEME_KEY, "formal" );
186 }
187 }
188
189 /**
190 * Set a directory for ultimate replication of the generated documentation.
191 * @param dir the utilimate destination directory
192 */
193 public void setDest( File dir )
194 {
195 m_dest = dir;
196 }
197
198 /**
199 * Set the doc theme.
200 * @param theme the theme name
201 */
202 public void setTheme( final String theme )
203 {
204 m_theme = theme;
205 }
206
207 /**
208 * Initialize the task.
209 * @exception BuildException if a build error occurs
210 */
211 public void init() throws BuildException
212 {
213 if( !isInitialized() )
214 {
215 super.init();
216 final Project project = getProject();
217 project.setNewProperty( ORG_NAME_KEY, ORG_NAME_VALUE );
218 project.setNewProperty( DOC_SRC_KEY, DOC_SRC_VALUE );
219 project.setNewProperty( DOC_RESOURCES_KEY, DOC_RESOURCES_VALUE );
220 project.setNewProperty( DOC_FORMAT_KEY, DOC_FORMAT_VALUE );
221 project.setNewProperty( DOC_DATE_FORMAT_KEY, DOC_DATE_FORMAT_VALUE );
222 project.setNewProperty( DOC_STYLE_KEY, DOC_STYLE_VALUE );
223 project.setNewProperty( DOC_ENTRY_KEY, DOC_ENTRY_VALUE );
224 project.setNewProperty( DOC_TEMP_KEY, DOC_TEMP_VALUE );
225 project.setNewProperty( DOC_LOGO_RIGHT_FILE_KEY, DOC_LOGO_RIGHT_FILE_VALUE );
226 project.setNewProperty( DOC_LOGO_RIGHT_URL_KEY, DOC_LOGO_RIGHT_URL_VALUE );
227 project.setNewProperty( DOC_LOGO_LEFT_FILE_KEY, DOC_LOGO_LEFT_FILE_VALUE );
228 project.setNewProperty( DOC_LOGO_LEFT_URL_KEY, DOC_LOGO_LEFT_URL_VALUE );
229 project.setNewProperty( DOC_LOGO_MIDDLE_FILE_KEY, DOC_LOGO_MIDDLE_FILE_VALUE );
230 project.setNewProperty( DOC_LOGO_MIDDLE_URL_KEY, DOC_LOGO_MIDDLE_URL_VALUE );
231 project.setNewProperty( DOC_BRAND_NAME_KEY, DOC_BRAND_NAME_VALUE );
232 }
233 }
234
235 /**
236 * Execute the task.
237 */
238 public void execute()
239 {
240 final Project project = getProject();
241 final File srcDir = getContext().getTargetBuildDocsDirectory();
242 if( !srcDir.exists() )
243 {
244 return;
245 }
246 log( "Filtered source: " + srcDir.getAbsolutePath() );
247
248 //
249 // create the temporary directory into which we generate the
250 // navigation structure (normally target/temp/docs)
251 //
252
253 final File temp = getContext().getTargetTempDirectory();
254 final File destDir = new File( temp, "docs" );
255 mkDir( destDir );
256
257 //
258 // get the theme, output formats, etc.
259 //
260
261 final File docs = getContext().getTargetDocsDirectory();
262 log( "Destination: " + docs.getAbsolutePath() );
263 mkDir( docs );
264 final String theme = getTheme();
265 final String output = getOutputFormat();
266 final String home = getHomePath();
267 final File themeRoot = getThemesDirectory();
268 final File themeDir = new File( themeRoot, theme + "/" + output );
269
270 final File target = getContext().getTargetDirectory();
271 final String resourcesPath = project.getProperty( DOC_RESOURCES_KEY );
272 final File resources = new File( target, resourcesPath );
273
274 log( "Year: " + getYear() );
275 log( "Theme: " + themeDir );
276
277 //
278 // initiate the transformation starting with the generation of
279 // the navigation structure based on the src directory content
280 // into the temporary destingation directory, copy the content
281 // sources to to the temp directory, transform the content and
282 // generated navigation in the temp dir using the selected them
283 // into the final docs directory, and copy over resources to
284 // the final docs directory
285 //
286
287 try
288 {
289 transformNavigation( themeDir, srcDir, destDir );
290 copySources( srcDir, destDir );
291 transformDocs( themeDir, destDir, docs );
292 copyThemeResources( themeDir, docs );
293 copySrcResources( resources, docs );
294 }
295 catch( BuildException e )
296 {
297 throw e;
298 }
299 catch( Throwable e )
300 {
301 log( "XSLT execution failed: " + e.getMessage() );
302 throw new BuildException( e );
303 }
304
305 if( null != m_dest )
306 {
307 copy( docs, m_dest, "**/*", "" );
308 }
309 }
310
311 private File getThemesDirectory()
312 {
313 return new File( Transit.DPML_PREFS, "dpml/tools/themes" );
314 }
315
316 private String getHomePath()
317 {
318 return getProject().getProperty( DOC_HOME_PATH_KEY );
319 }
320
321 private String getOutputFormat()
322 {
323 return getProject().getProperty( DOC_FORMAT_KEY );
324 }
325
326 private String getOutputStyle()
327 {
328 return getProject().getProperty( DOC_STYLE_KEY );
329 }
330
331 private String getDateFormat()
332 {
333 return getProject().getProperty( DOC_DATE_FORMAT_KEY );
334 }
335
336 private void transformNavigation( final File themeDir, final File source, final File dest )
337 {
338 final File xslFile = new File( themeDir, "nav-aggregate.xsl" );
339 if( !xslFile.exists() )
340 {
341 return; // Theme may not use navigation.
342 }
343 log( "Transforming navigation." );
344 try
345 {
346 transformTrax(
347 source, dest, xslFile,
348 "^.*/navigation.xml$", "", ".xml" );
349 }
350 catch( BuildException e )
351 {
352 throw e;
353 }
354 catch( Throwable e )
355 {
356 final String error =
357 "Transformation failure: " + source;
358 throw new BuildException( error, e );
359 }
360 }
361
362 private void copySources( final File source, final File dest )
363 {
364 copy( source, dest, "**/*", "**/navigation.xml" );
365 }
366
367 private void transformDocs( final File themeDir, final File build, final File docs )
368 {
369 String style = getOutputStyle();
370 File xslFile = new File( themeDir, style + ".xsl" );
371 String output = getOutputFormat();
372 log( "Transforming content." );
373 transformTrax(
374 build, docs, xslFile,
375 "^.*\\.xml$", "^.*/navigation.xml$", "." + output );
376 }
377
378 private void copySrcResources( final File resources, final File docs )
379 {
380 copy( resources, docs, "**/*", "" );
381 }
382
383 private void copyThemeResources( final File themeDir, final File docs )
384 {
385 final File fromDir = new File( themeDir, "resources" );
386 copy( fromDir, docs, "**/*", "" );
387 }
388
389 private void copy( final File fromDir, final File toDir, final String includes, final String excludes )
390 {
391 if( !fromDir.exists() )
392 {
393 return;
394 }
395
396 final FileSet from = new FileSet();
397 from.setDir( fromDir );
398 from.setIncludes( includes );
399 from.setExcludes( excludes );
400
401 mkDir( toDir );
402
403 final Copy copy = (Copy) getProject().createTask( "copy" );
404 copy.setTodir( toDir );
405 copy.addFileset( from );
406 copy.setPreserveLastModified( true );
407 copy.execute();
408 }
409
410
411 private void transformTrax(
412 final File srcDir, final File toDir, final File xslFile,
413 final String includes, final String excludes, final String extension )
414 throws BuildException
415 {
416 FileInputStream fis = null;
417 try
418 {
419 ClassLoader cl = getClass().getClassLoader();
420 Thread.currentThread().setContextClassLoader( cl );
421 StreamSource source = new StreamSource( xslFile );
422 final TransformerFactory tfactory = TransformerFactory.newInstance();
423 final Transformer transformer = tfactory.newTransformer( source );
424 final RegexpFilter filter = new RegexpFilter( includes, excludes );
425
426 m_baseToDir = toDir;
427 m_baseSrcDir = srcDir.getAbsoluteFile();
428 String entrypoint = getEntryPoint();
429 if( "".equals( entrypoint ) )
430 {
431 transform( transformer, m_baseSrcDir, toDir, filter, extension );
432 }
433 else
434 {
435 File fileToConvert = new File( m_baseSrcDir, entrypoint );
436 transformFile(
437 transformer, fileToConvert, m_baseSrcDir, entrypoint, extension, m_baseToDir );
438 }
439 }
440 catch( BuildException e )
441 {
442 throw e;
443 }
444 catch( Exception e )
445 {
446 throw new BuildException( e.getMessage(), e );
447 }
448 finally
449 {
450 if( fis != null )
451 {
452 try
453 {
454 fis.close();
455 }
456 catch( IOException f )
457 {
458 log( f.toString() );
459 }
460 }
461 }
462 }
463
464 private void transform( final Transformer transformer, final File srcDir, final File toDir,
465 final FileFilter filter, final String extension )
466 throws BuildException
467 {
468 boolean recursive = isRecursive();
469 final File[] content = srcDir.listFiles( filter );
470 for( int i=0; i < content.length; i++ )
471 {
472 String base = content[i].getName();
473 if( content[i].isDirectory() && recursive )
474 {
475 final File newDest = new File( toDir, base );
476 newDest.mkdirs();
477 transform( transformer, content[i], newDest, filter, extension );
478 }
479 if( content[i].isFile() )
480 {
481 transformFile( transformer, content[i], srcDir, base, extension, toDir );
482 }
483 }
484 }
485
486 private void transformFile( Transformer transformer, File content, File srcDir,
487 String base, String extension, File toDir )
488 {
489 String userDir = System.getProperty( "user.dir" );
490 System.setProperty( "user.dir", toDir.getAbsolutePath() );
491 final String year = getYear();
492 final String org = getOrganization();
493 final String copyright =
494 "Copyright "
495 + year
496 + ", "
497 + org
498 + " All rights reserved.";
499
500 final String svnRoot = getProject().getProperty( DOC_ANCHOR_URL_KEY );
501 final String svnSource = svnRoot + getRelSrcPath( srcDir ) + "/" + base;
502
503 final int pos = base.lastIndexOf( '.' );
504 if( pos > 0 )
505 {
506 base = base.substring( 0, pos );
507 }
508 base = base + extension;
509
510 final File newDest = new File( toDir, base );
511 final StreamSource xml = new StreamSource( content );
512 final StreamResult out = new StreamResult( newDest );
513
514 transformer.clearParameters();
515 transformer.setParameter( "directory", getRelToPath( toDir ) );
516 transformer.setParameter( "fullpath", getRelToPath( newDest ) );
517 transformer.setParameter( "file", base );
518 transformer.setParameter( "svn-location", svnSource );
519 transformer.setParameter( "copyright", copyright );
520 transformer.setParameter(
521 "logoright_file",
522 getProject().getProperty( DOC_LOGO_RIGHT_FILE_KEY ).trim() );
523 transformer.setParameter(
524 "logoright_url",
525 getProject().getProperty( DOC_LOGO_RIGHT_URL_KEY ).trim() );
526 transformer.setParameter(
527 "logoleft_file",
528 getProject().getProperty( DOC_LOGO_LEFT_FILE_KEY ).trim() );
529 transformer.setParameter(
530 "logoleft_url",
531 getProject().getProperty( DOC_LOGO_LEFT_URL_KEY ).trim() );
532 transformer.setParameter(
533 "logomiddle_file",
534 getProject().getProperty( DOC_LOGO_MIDDLE_FILE_KEY ).trim() );
535 transformer.setParameter(
536 "logomiddle_url",
537 getProject().getProperty( DOC_LOGO_MIDDLE_URL_KEY ).trim() );
538 transformer.setParameter(
539 "brand_name",
540 getProject().getProperty( DOC_BRAND_NAME_KEY ).trim() );
541 transformer.setParameter( "generated_date", getNow() );
542 setOtherProperties( transformer );
543 try
544 {
545 transformer.transform( xml, out );
546 }
547 catch( BuildException e )
548 {
549 throw e;
550 }
551 catch( Throwable e )
552 {
553 final String error =
554 "An error occured while attempting to transform document: "
555 + getRelToPath( newDest );
556 throw new BuildException( error, e, getLocation() );
557 }
558 finally
559 {
560 System.setProperty( "user.dir", userDir );
561 }
562 }
563
564 private String getRelToPath( final File dir )
565 {
566 final String basedir = m_baseToDir.getAbsolutePath();
567 final String curdir = dir.getAbsolutePath();
568 return curdir.substring( basedir.length() );
569 }
570
571 private String getRelSrcPath( final File dir )
572 {
573 final String basedir = m_baseSrcDir.getAbsolutePath();
574 final String curdir = dir.getAbsolutePath();
575 return curdir.substring( basedir.length() );
576 }
577
578 /**
579 * Utility regualar expression filter.
580 */
581 public class RegexpFilter implements FileFilter
582 {
583 private Pattern m_includes;
584 private Pattern m_excludes;
585
586 /**
587 * New filter creation.
588 * @param includes the includes
589 * @param excludes the excludes
590 */
591 public RegexpFilter( final String includes, final String excludes )
592 {
593 m_includes = Pattern.compile( includes );
594 m_excludes = Pattern.compile( excludes );
595 }
596
597 /**
598 * Test supplied file for acceptance.
599 * @param file the candidate
600 * @return TRUE if acceptable
601 */
602 public boolean accept( final File file )
603 {
604 final String basename = file.getName();
605
606 if( basename.equals( ".svn" ) )
607 {
608 return false;
609 }
610
611 if( basename.equals( "CVS" ) )
612 {
613 return false;
614 }
615
616 if( file.isDirectory() )
617 {
618 return true;
619 }
620
621 final String fullpath = file.getAbsolutePath().replace( '\\', '/' );
622
623 Matcher m = m_includes.matcher( fullpath );
624 if( !m.matches() )
625 {
626 return false;
627 }
628
629 m = m_excludes.matcher( fullpath );
630 return !m.matches();
631 }
632 }
633
634 private String getYear()
635 {
636 String year = getProject().getProperty( "magic.year" );
637 if( year != null )
638 {
639 return year;
640 }
641 else
642 {
643 Calendar cal = Calendar.getInstance();
644 return Integer.toString( cal.get( Calendar.YEAR ) );
645 }
646 }
647
648 private String getOrganization()
649 {
650 return getProject().getProperty( ORG_NAME_KEY );
651 }
652
653 private boolean isRecursive()
654 {
655 return "".equals( getEntryPoint() );
656 }
657
658 private String getEntryPoint()
659 {
660 String point = getProject().getProperty( DOC_ENTRY_KEY );
661 if( point == null )
662 {
663 return "";
664 }
665 return point;
666 }
667
668 private void setOtherProperties( Transformer transformer )
669 {
670 String prefix = "project.docs.xsl.";
671 int prefixLen = prefix.length();
672
673 Hashtable p = getProject().getProperties();
674 Iterator list = p.entrySet().iterator();
675 while( list.hasNext() )
676 {
677 Map.Entry entry = (Map.Entry) list.next();
678 String key = (String) entry.getKey();
679 if( key.startsWith( prefix ) )
680 {
681 String value = (String) entry.getValue();
682 key = key.substring( prefixLen );
683 transformer.setParameter( key, value );
684 log( "Setting " + key + "=" + value );
685 }
686 }
687 }
688
689 private String getNow()
690 {
691 Date now = new Date();
692 SimpleDateFormat sdf = new SimpleDateFormat( getDateFormat() );
693 String result = sdf.format( now );
694 return result;
695 }
696 }